/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.web30.nntp.service;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.net.MalformedServerReplyException;
import org.apache.commons.net.nntp.ArticlePointer;
import org.apache.commons.net.nntp.NNTPClient;
import org.apache.commons.net.nntp.NNTPReply;
import org.apache.commons.net.nntp.NewGroupsOrNewsQuery;
import org.apache.commons.net.nntp.NewsgroupInfo;
import org.apache.james.mime4j.field.ContentTypeField;
import org.apache.james.mime4j.field.FieldName;
import org.apache.james.mime4j.message.BodyPart;
import org.apache.james.mime4j.message.Header;
import org.apache.james.mime4j.message.Message;
import org.apache.james.mime4j.message.Multipart;
import org.apache.james.mime4j.parser.Field;
import org.apache.james.mime4j.util.CharsetUtil;

import org.web30.nntp.Util;

/**
 *
 * @author Xu Tiejun
 */
public class NNTPService {

    public static String DATE_FORMAT = "yyyy/MM/dd'T00:00:00.SSS'Z";
    private NNTPClientExt conn = null;
    private String hostname = null;
    private int port = 119;
    private String user = null;
    private String password = null;
    private String ommitKey = null;
    private String ommitValue = null;

    /**
     * NewsRecords is inner class of NNTPService. It is a list of all queried
     * article number and ids. You can use
     * {@link org.web30.nntp.service.NNTPService.NewsRecords hasNext} to check
     * wheather has more articles, and use
     * {@link org.web30.nntp.service.NNTPService.NewsRecords next} to get a
     * Reader to aritcle.
     * <p>
     */
    public class NewsRecords {

        private int readIndex = 0;
        private BufferedReader input = null;
        private ArrayList<ArticlePointer> articleList = null;

        NewsRecords(ArrayList<ArticlePointer> alist) {
            if (alist != null) {
                articleList = new ArrayList<ArticlePointer>(alist);
            } else {
                articleList = null;
            }
        }

        public boolean hasNext() {
            return hasNext(true);
        }

        /**
         * Check wheather has more article to read .
         *
         * @return True if the has more aritcles, false otherwise.
         */
        public boolean hasNext(boolean isOmmit) {
            if (articleList == null) {
                return false;
            }
            boolean isFindArticleToReturn = false;
            for (; !isFindArticleToReturn; readIndex++) {
                boolean ommitArticle = false;
                if (readIndex >= articleList.size()) {
                    return false;
                }
                if (!isOmmit) {
                    return true;
                }
                ArticlePointer ap = (ArticlePointer) articleList.get(readIndex);
                if (ommitKey != null && ommitValue != null) {
                    try {
                        Reader in = NNTPService.this.conn.retrieveArticleHeader(ap.articleNumber);
                        if (in == null) {
                            Logger.getLogger(NNTPService.class.getName()).log(
                                    Level.WARNING,
                                    "retrieveArticleHeader failed with articleNumber:{0}",
                                    Integer.toString(ap.articleNumber));
                            continue;
                        }
                        BufferedReader input = new BufferedReader(in);
                        String line = null;
                        while ((line = input.readLine()) != null) {
                            if (checkOmmitKeyAndVal(line,
                                    NNTPService.this.ommitKey,
                                    NNTPService.this.ommitValue)) {
                                String[] logarg2 = {
                                    Integer.toString(ap.articleNumber),
                                    ap.articleId};
                                Logger.getLogger(NNTPService.class.getName()).log(
                                        Level.INFO,
                                        "ommited articleNumber:{0}, articleId:{1}",
                                        logarg2);
                                ommitArticle = true;
                                break;
                            }
                        }
                        input.close();
                        if (!ommitArticle) {
                            isFindArticleToReturn = true;
                            break;
                        }
                    } catch (IOException ex) {
                    }
                } else {
                    return true;
                }
            }
            return true;
        }

        /**
         * Get next article id will be read. Shoule called before
         * {@link org.web30.nntp.service.NNTPService.NewsRecords next}
         *
         * @return Returns article id will be read. null if no articles.
         */
        public String getArticleId() {
            if (articleList == null || readIndex >= articleList.size()) {
                return null;
            }

            return ((ArticlePointer) articleList.get(readIndex)).articleId;
        }

        /**
         * Get articles count of this record.
         *
         * @return Returns article count.
         */
        public int getRecordCount() {
            if (articleList == null) {
                return 0;
            }

            return articleList.size();
        }

        /**
         * Get next article number will be read. Shoule called before
         * {@link org.web30.nntp.service.NNTPService.NewsRecords next}
         *
         * @return Returns article id will be read. -1 if no articles.
         */
        public int getArticleNumber() {
            if (articleList == null || readIndex >= articleList.size()) {
                return -1;
            }

            return ((ArticlePointer) articleList.get(readIndex)).articleNumber;
        }

        /**
         * Retrieves next article in record. A BufferedReader is returned from
         * which the article can be read. If the article does not exist or
         * IOException occured, null is returned.
         *
         * @return Returns a BufferedReader to next article.
         */
        public BufferedReader next() {
            close();

            if (articleList == null) {
                return null;
            }
            Reader in = null;
            int articleNumber = getArticleNumber();
            try {
                if (articleNumber == -1) {
                    String articleId = this.getArticleId();
                    if (articleId != null) {
                        in = NNTPService.this.conn.retrieveArticle(articleId);
                    } else {
                        return null;
                    }
                } else {
                    in = NNTPService.this.conn.retrieveArticle(articleNumber);
                }
                input = new BufferedReader(in);
            } catch (IOException e) {
                Logger.getLogger(NNTPService.class.getName()).log(Level.INFO,
                        "retrieveArticle failed with articleNumber:{0}",
                        Integer.toString(articleNumber));
            }
            readIndex++;
            return input;
        }
        final static String SUBJECT_LEN = "Subject: ";
        final static String CHARSET = "charset";

        public Message nextMime() {
            BufferedReader br = next();
            if (br == null) {
                return null;
            }

            Message mime = Util.getMimeObj(br);

            if (mime == null) {
                return null;
            }

            Header header = mime.getHeader();
            if (header == null) {
                return mime;
            }

            ContentTypeField ctField = (ContentTypeField) header.getField(FieldName.CONTENT_TYPE);
            if (ctField == null) {
                return mime;
            }
            if (!ctField.isMultipart()) {
                String mimeCharset = ContentTypeField.getCharset(ctField);
                String javaCharset = CharsetUtil.toJavaCharset(mimeCharset);
                Field field = header.getField(FieldName.SUBJECT);
                byte[] rawByte = field.getRaw().toByteArray();
                String test = "";
                try {
                    test = new String(rawByte, SUBJECT_LEN.length(),
                            rawByte.length - SUBJECT_LEN.length(), javaCharset);
                } catch (UnsupportedEncodingException e) {
                }
                if (CharsetUtil.isASCII(test)) {
//					System.out.println("isASCII");
                } else {
                    mime.setSubject(test);
                }
            } else {
                Multipart parts = (Multipart) mime.getBody();
                List<BodyPart> bodyParts = parts.getBodyParts();
                for (BodyPart part : bodyParts) {
                }
            }
            return mime;
        }

        /**
         * Close reader in record. Only called when
         * {@link org.web30.nntp.service.NNTPService.NewsRecords hasNext}
         * retuened false.
         */
        public void close() {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                }
                input = null;
            }
        }
    }

    /**
     * NewsWriter is inner class of NNTPService. It is a utility class to post
     * article. Use
     * {@link org.web30.nntp.service.NNTPService.NewsWriter hasNext} to check
     * wheather the client can post articles, and use
     * {@link org.web30.nntp.service.NNTPService.NewsWriter next} to get a new
     * writer to post article.
     * <p>
     */
    public class NewsWriter {
        private int lastRet = -1;
        private String lastId = null;

        public String getLastId() {
            return lastId;
        }

        public int getLastRet() {
            return lastRet;
        }
      
        private BufferedWriter output = null;

        /**
         * Check wheather the client can post articles
         *
         * @return True if the client can post articles to the server, false
         *         otherwise.
         */
        public boolean hasNext() {
            if (!NNTPService.this.conn.isAllowedToPost()) {
                return false;

            } else {
                return true;
            }
        }

        /**
         * Get a new Writet to post article. A BufferWriter is returned to the
         * post new article. null will be returned if IOException occured.
         *
         * @return Returns a BufferedWriter to new article.
         */
        public BufferedWriter next() {
            Writer out = null;
            close();
            try {
                out = NNTPService.this.conn.postArticle();
            } catch (IOException e) {
                Logger.getLogger(NNTPService.class.getName()).log(
                        Level.WARNING, "postArticle failed.");
            }

            if (out == null) {
                return null;
            }

            output = new BufferedWriter(out);
            return output;
        }

        /**
         * Close inner writer. It will be called in
         * {@link org.web30.nntp.service.NNTPService.NewsWriter completePendingCommand}.
         *
         */
        public void close() {
            try {
                if (output != null) {
                    output.close();
                    output = null;
                }
            } catch (IOException ex) {
                Logger.getLogger(NNTPService.class.getName()).log(
                        Level.WARNING, "close failed.", ex);
            }
        }

        /**
         * Close inner writer and check wheather post successfully.
         *
         * @return True if successfully completed, false if not.
         */
        public boolean completePendingCommand() {
            try {
                close();
                this.lastRet = NNTPService.this.conn.getReply();                
                boolean result = NNTPReply.isPositiveCompletion(this.lastRet);
                if (!result) {
                    Logger.getLogger(NNTPService.class.getName()).log(
                            Level.WARNING,
                            "completePendingCommand replycode is ({0})",
                            Integer.toString(this.lastRet));
                } else {
                    String temp = NNTPService.this.conn.getReplyString().trim();
                    if(this.lastRet == NNTPReply.ARTICLE_POSTED_OK) {
                        this.lastId = temp.substring(temp.indexOf("<")+1,temp.length()-1);
                    }
                }
                return result;
            } catch (IOException ex) {
                Logger.getLogger(NNTPService.class.getName()).log(
                        Level.WARNING, null, ex);
            }
            return false;
        }
    }

    public NNTPService(String host, int port, String user, String password) {
        if (host == null) {
            this.hostname = "Localhost";
        }
        this.hostname = host;
        this.port = port;
        this.user = user;
        this.password = password;
        this.conn = new NNTPClientExt();
    }

    /**
     * Open NNTP connection
     *
     * @throws SocketException
     * @throws IOException
     */
    public void init() throws SocketException, IOException {
        this.conn.connect(this.hostname, this.port);
        if (this.user != null && this.password != null) {
            this.conn.authenticate(this.user, this.password);
        }
    }

    /**
     * Close NNTP connection
     *
     * @throws IOException
     */
    public void close() {

        try {
            if (this.user != null && this.password != null) {
                this.conn.logout();
            }
            this.conn.disconnect();
        } catch (IOException ex) {
            Logger.getLogger(NNTPService.class.getName()).log(Level.WARNING,
                    "NNTPClient disconnect failed.", ex);
        }
    }

    /**
     * Get article by article id
     * <p>
     *
     * @param newsGroup
     *            The newsgroup to select.
     *
     * @param articleId
     *            article id of message
     *
     * @return read record of articles
     */
    public NewsRecords getNewsById(String newsGroup, String articleId) throws IOException {
        if (!this.conn.isConnected()) {
            Logger.getLogger(NNTPService.class.getName()).log(Level.WARNING,
                    "NNTPClient disconnected.");
            return new NewsRecords(null);
        }

        NewsgroupInfo info = new NewsgroupInfo();
        this.conn.selectNewsgroup(newsGroup, info);

        if (this.conn.getReplyCode() != NNTPReply.CODE_211) {
            return new NewsRecords(null);
        }
        if (info.getArticleCount() == 0) {
            Logger.getLogger(NNTPService.class.getName()).log(Level.INFO,
                    "group ({0}) has no atricles.", newsGroup);
            return new NewsRecords(null);
        }
        ArticlePointer pointer = new ArticlePointer();
        this.conn.selectArticle(articleId, pointer);
        if (this.conn.getReplyCode() != NNTPReply.CODE_223) {
            return new NewsRecords(null);
        }
        ArrayList<ArticlePointer> msg = new ArrayList<ArticlePointer>(1);
        if (pointer.articleNumber == 0) {
            pointer.articleNumber = -1;
        }
        msg.add(pointer);
        return new NewsRecords(msg);
    }

    public String getReferenceById(String newsGroup,
            String articleId) throws IOException {
        if (!this.conn.isConnected()) {
            Logger.getLogger(NNTPService.class.getName()).log(Level.WARNING,
                    "NNTPClient disconnected.");
            return null;
        }
        try {
            NewsgroupInfo info = new NewsgroupInfo();
            this.conn.selectNewsgroup(newsGroup, info);

            if (this.conn.getReplyCode() != NNTPReply.CODE_211) {
                return null;
            }
            if (info.getArticleCount() == 0) {
                Logger.getLogger(NNTPService.class.getName()).log(Level.INFO,
                        "group ({0}) has no atricles.", newsGroup);
                return null;
            }
            String paras = String.format("References %s *",articleId);
            this.conn.sendCommand("xpat",
                   paras);
            String references = null;
            BufferedReader reader = this.conn.getReader();
            references = reader.readLine();
            reader.close();
            return references;
        } catch (Exception e) {
            Logger.getLogger(NNTPService.class.getName()).log(Level.INFO,
                    "group ({0}) has no atricles.", newsGroup);
            return null;
        }        
    }
    /**
     * Get articles of today
     * <p>
     *
     * @param newsGroup
     *            The newsgroup to select.
     *
     * @return read record of articles
     */
    public NewsRecords getDailyNews(String newsGroup) throws IOException {
        SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
        Date date = new Date();
        return this.getNewsByDate(newsGroup, dateFormat.format(date));
    }
    private int _getArticleNumberInXpat(String ret){
        int articleNumber = -1;
        if(ret == null)
            return -1;
        int index = ret.indexOf(" ");
        if(index < 0)
            return -1;
        String number = ret.substring(0,index).trim();
        try{
            articleNumber = Integer.parseInt(number);
        }catch (Exception e){
            return -1;
        }
        return articleNumber;

    }
    private ArrayList<ArticlePointer> _getCommentsIds() throws IOException{
        if(this.conn.getReader() == null)
            return null;
        ArrayList<ArticlePointer> msg = new ArrayList<ArticlePointer>();
        String line = null;
        BufferedReader reader = this.conn.getReader();
        while((line = reader.readLine() )!= null){
            int articleNumber = this._getArticleNumberInXpat(line);
            if( articleNumber > 0){
                ArticlePointer pointer = new ArticlePointer();
                pointer.articleNumber = articleNumber;
                msg.add(pointer);
            }
        }
        reader.close();
        return msg;
    }
    /**
     * Get articles reply for specificed message
     * <p>
     *
     * @param newsGroup
     *            The newsgroup to select.
     *
     * @param id
     *            The message id. The returned messages are reply for this message
     *
     * @return read record of articles
     */
    public NewsRecords getCommentsById(String newsGroup, String id) {
        if (!this.conn.isConnected()) {
            Logger.getLogger(NNTPService.class.getName()).log(Level.WARNING,
                    "NNTPClient disconnected.");
            return new NewsRecords(null);
        }
        try {
            NewsgroupInfo info = new NewsgroupInfo();
            this.conn.selectNewsgroup(newsGroup, info);

            if (this.conn.getReplyCode() != NNTPReply.CODE_211) {
                return new NewsRecords(null);
            }
            if (info.getArticleCount() == 0) {
                Logger.getLogger(NNTPService.class.getName()).log(Level.INFO,
                        "group ({0}) has no atricles.", newsGroup);
                return new NewsRecords(null);
            }
            String paras = String.format("References %s-%s *%s*",
                    info.getFirstArticle(), info.getLastArticle(), id);
            this.conn.sendCommand("xpat",
                   paras);
            ArrayList<ArticlePointer> ids = this._getCommentsIds();
            if(ids == null){
                return new NewsRecords(null);
            }
            return new NewsRecords(ids);
        } catch (Exception e) {
            Logger.getLogger(NNTPService.class.getName()).log(Level.INFO,
                    "group ({0}) has no atricles.", newsGroup);
            return new NewsRecords(null);
        }
    }
    /**
     * Get articles after specified time
     * <p>
     *
     * @param newsGroup
     *            The newsgroup to select.
     *
     * @param timeStamp
     *            will return articles after this time stamps
     *            time stamp format NNTService.DATE_FORMAT. yyyy/MM/dd'T00:00:00.SSS'Z
     *            
     *
     * @return read record of articles
     */
    public NewsRecords getNewsByDate(String newsGroup, String timeStamp) throws IOException {
        if (!this.conn.isConnected()) {
            Logger.getLogger(NNTPService.class.getName()).log(Level.WARNING,
                    "NNTPClient disconnected.");
            return new NewsRecords(null);
        }
        try {
            NewsgroupInfo info = new NewsgroupInfo();
            this.conn.selectNewsgroup(newsGroup, info);

            if (this.conn.getReplyCode() != NNTPReply.CODE_211) {
                return new NewsRecords(null);
            }
            if (info.getArticleCount() == 0) {
                Logger.getLogger(NNTPService.class.getName()).log(Level.INFO,
                        "group ({0}) has no atricles.", newsGroup);
                return new NewsRecords(null);
            }
            SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
            Date date = sdf.parse(timeStamp);
            GregorianCalendar calendar = new GregorianCalendar();
            calendar.setTime(date);
            NewGroupsOrNewsQuery query = new NewGroupsOrNewsQuery(
                    calendar, false);
            query.addNewsgroup(newsGroup);
            String[] msgIds = this.conn.listNewNews(query);
            ArrayList<ArticlePointer> articles = new ArrayList<ArticlePointer>(msgIds.length);
            for (String id : msgIds) {
                ArticlePointer pointer = new ArticlePointer();
                pointer.articleId = id;
                pointer.articleNumber = -1;
                articles.add(pointer);
            }
            return new NewsRecords(articles);
        } catch (Exception e) {
            Logger.getLogger(NNTPService.class.getName()).log(Level.INFO,
                    "group ({0}) has no atricles.", newsGroup);
            return new NewsRecords(null);
        }
    }

    /** * Same as <code> getNewNews(newsGroup, null) </code> ** */
    public NewsRecords getNewNews(String newsGroup)
            throws MalformedServerReplyException {
        return getNewNews(newsGroup, null);
    }

    /**
     * Get articles posted after specific article id in specific group.
     * <p>
     *
     * @param newsGroup
     *            The newsgroup to select.
     *
     * @param articaleId
     *            The unique article identifier of the article whose header is
     *            being retrieved. If this parameter is null, all articles in
     *            this group will be returned.
     *
     * @return read record of articles
     */
    public NewsRecords getNewNews(String newsGroup, String articleId) {

        if (!this.conn.isConnected()) {
            Logger.getLogger(NNTPService.class.getName()).log(Level.WARNING,
                    "NNTPClient disconnected.");
            return new NewsRecords(null);
        }

        try {

            NewsgroupInfo info = new NewsgroupInfo();
            this.conn.selectNewsgroup(newsGroup, info);

            if (this.conn.getReplyCode() != NNTPReply.CODE_211) {
                return new NewsRecords(null);
            }

            if (info.getArticleCount() == 0) {
                Logger.getLogger(NNTPService.class.getName()).log(Level.INFO,
                        "group ({0}) has no atricles.", newsGroup);
                return new NewsRecords(null);
            } else {
                String[] logarg = {newsGroup,
                    Integer.toString(info.getArticleCount())};
                Logger.getLogger(NNTPService.class.getName()).log(Level.INFO,
                        "group ({0}) has {1} atricles.", logarg);
            }

            if (articleId == null) {
                return _getNewNews(info.getFirstArticle(), info.getLastArticle());
            }

            ArticlePointer pointer = new ArticlePointer();
            if (!this.conn.selectArticle(articleId, pointer)) {
                Logger.getLogger(NNTPService.class.getName()).log(
                        Level.WARNING,
                        "selectArticle failed with articleId: {0}", articleId);
                return new NewsRecords(null);
            }

            if ((pointer.articleNumber == 0) ||
                    (pointer.articleNumber < info.getFirstArticle()) ||
                    (pointer.articleNumber > info.getLastArticle())) {
                pointer.articleNumber = _getArticleNumberById(articleId, newsGroup);
                if (pointer.articleNumber == -1) {
                    Logger.getLogger(NNTPService.class.getName()).log(
                            Level.WARNING, "Not found articleId : {0}",
                            articleId);
                    return new NewsRecords(null);
                }
            }

            String[] logarg = {articleId,
                Integer.toString(pointer.articleNumber)};
            Logger.getLogger(NNTPService.class.getName()).log(Level.INFO,
                    "By STAT Found articleId: {0}, articleNumber: {1}", logarg);

            if (pointer.articleNumber == info.getLastArticle()) {
                String[] logarg2 = {newsGroup,
                    Integer.toString(pointer.articleNumber),
                    pointer.articleId};
                Logger.getLogger(NNTPService.class.getName()).log(
                        Level.INFO,
                        "group ({0}) has no news newer than articleNumber[{1}], articleId {2}",
                        logarg2);

                return new NewsRecords(null);
            }
            return _getNewNews(pointer.articleNumber, info.getLastArticle());
        } catch (Exception ex) {
            Logger.getLogger(NNTPService.class.getName()).log(Level.SEVERE,
                    null, ex);
        }
        return new NewsRecords(null);
    }

    private int _getArticleNumberById(String messageId, String groupName) {
        int retValue = -1;
        try {
            Reader in = this.conn.retrieveArticleHeader(messageId);
            BufferedReader input = new BufferedReader(in);
            do {
                String str = input.readLine();
                if (str == null) {
                    break;
                }
                if (str.startsWith("Xref")) {
                    StringTokenizer token = new StringTokenizer(str,
                            " \t\n\r\f:");
                    while (token.hasMoreTokens()) {
                        String t = token.nextToken();
                        if (t.equalsIgnoreCase(groupName)) {
                            if (token.hasMoreElements()) {
                                retValue = Integer.parseInt(token.nextToken(), 10);
                                break;
                            }
                        }
                    }
                    break;
                }
            } while (true);
        } catch (IOException e) {
            Logger.getLogger(NNTPService.class.getName()).log(Level.SEVERE,
                    null, e);
            return retValue;
        }
        return retValue;
    }

    private NewsRecords _getNewNews(int min, int max) throws IOException {
        String[] logarg = {Integer.toString(min), Integer.toString(max),
            Integer.toString(max - min + 1)};
        Logger.getLogger(NNTPService.class.getName()).log(Level.INFO,
                "Get article from {0} to {1}, {2} items.", logarg);
        ArrayList<ArticlePointer> articles = new ArrayList<ArticlePointer>(max - min + 1);
        for (int index = min; index <= max; index++) {
            ArticlePointer pointer = new ArticlePointer();
            if (!this.conn.selectArticle(index, pointer)) {
                Logger.getLogger(NNTPService.class.getName()).log(
                        Level.WARNING,
                        "selectArticle failed with articleNumber:{0}",
                        Integer.toString(index));
                continue;
            }
            articles.add(pointer);
        }
        return new NewsRecords(articles);
    }

    private boolean checkOmmitKeyAndVal(String line, String key, String value) {
        if (key == null || value == null) {
            return false;
        }
        if (!line.startsWith(key)) {
            return false;
        }
        int index = line.indexOf(':');
        if (index == -1) {
            return false;
        }
        String _key = line.substring(0, index).trim();
        String _val = line.substring(index + 1, line.length()).trim();
        if (_key.compareToIgnoreCase(key) == 0 && _val.compareToIgnoreCase(value) == 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Post new article to news group.
     *
     * @param newsGroup
     *            Post the article to this group. In fact, article is post to
     *            which group is defined by article header field "Newsgroups".
     *
     * @return A WriteRecord to post article. null if this group is not allowed
     *         to post article.
     */
    public NewsWriter postNews(String newsGroup) {
        try {
            NewsgroupInfo[] infos = this.conn.listNewsgroups();

            this.conn.retrieveArticleInfo(0);
            for (int i = 0; i < infos.length; i++) {
                if (infos[i].getNewsgroup().equals(newsGroup)) {
                    if (infos[i].getPostingPermission() == NewsgroupInfo.PERMITTED_POSTING_PERMISSION) {
                        return new NewsWriter();
                    } else {
                        Logger.getLogger(NNTPService.class.getName()).log(
                                Level.WARNING,
                                "group ({0}) has no permission to post.",
                                newsGroup);
                    }
                }
            }
            Logger.getLogger(NNTPService.class.getName()).log(Level.WARNING,
                    "Not found gropu ({0}) .", newsGroup);
        } catch (IOException ex) {
            Logger.getLogger(NNTPService.class.getName()).log(Level.SEVERE,
                    "listNewsgroups failed.");
        }
        return null;
    }

    /**
     * Get ommit key . Default is null.
     * <p>
     *
     * @return The ommit key set by user.
     */
    public String getOmmitKey() {
        return ommitKey;
    }

    /**
     * Set ommit key .
     * <p>
     *
     * @return The ommit key set by user.
     */
    public void setOmmitKey(String ommitKey) {
        this.ommitKey = ommitKey;
    }

    /**
     * Get ommit value . Default is null.
     *
     * @return The ommit value set by user.
     */
    public String getOmmitValue() {
        return ommitValue;
    }

    /**
     * Set ommit value .
     * <p>
     *
     * @return The ommit value set by user.
     */
    public void setOmmitValue(String ommitValue) {
        this.ommitValue = ommitValue;
    }

    public static void main(String[] strs) throws SocketException, IOException {

        NNTPClient conn = new NNTPClient();
        conn.connect("172.28.90.20", 119);

        NewsgroupInfo info = new NewsgroupInfo();
        conn.selectNewsgroup("trash", info);

        // "<20100111165324.E5FF.~*~*@172.28.90.20>"
        // "<1263195349_225@newsgroup.com>"
        // "<1263276723_275@newsgroup.com>"
        // "<1263279631_295@newsgroup.com>"
        String messageId = "<1264551500_11@newsgroup.com>";

        Reader in = null;// conn.retrieveArticle(messageId);
        BufferedReader input = null;// new BufferedReader(in);

        in = conn.retrieveArticleHeader(messageId);
        input = new BufferedReader(in);

        do {

            String str = input.readLine();
            if (str == null) {
                break;
            }

            if (str.startsWith("Xref")) {
                StringTokenizer token = new StringTokenizer(str, " \t\n\r\f:");
                while (token.hasMoreTokens()) {
                    String t = token.nextToken();
                    if (t.equalsIgnoreCase("trash")) {
                        if (token.hasMoreElements()) {
                            System.out.println("find::" + token.nextToken());
                            break;
                        } else {
                            System.out.println("failed");
                        }
                    }
                }
                break;
            }
        } while (true);

        input.close();
    }
}
